﻿using MongoDB.Driver;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.ESGRatings.BusinessLogic.Computing.Exceptions;
using Sustainalytics.ESGRatings.BusinessLogic.Computing.Mappers;
using Sustainalytics.ESGRatings.BusinessLogic.Computing.Utils;
using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.ESGRatings.Computing.Entities.ESG;
using Sustainalytics.ESGRatings.Computing.Entities.Governance;
using Sustainalytics.ESGRatings.DataContract.ESG;
using Sustainalytics.ESGRatings.Primitives;
using Sustainalytics.ESGRatings.Primitives.Exceptions;
using Sustainalytics.MongoDB.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{
    public class ComputationEngine : IDisposable
    {
        private static readonly Lazy<ComputationEngine> InstanceLazy = new Lazy<ComputationEngine>(() => new ComputationEngine());
        private readonly ESGSetList _esgSetList;
        private readonly ReaderWriterLockSlim _rwlSyncObject = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
        private static Action<string, Exception> _logging;
        private string _connectionString;

        private ComputationEngine()
        {
            try
            {
                _esgSetList = new ESGSetList();
            }
            catch (ESGException esgException)
            {
                _logging?.Invoke("Failed to create the ESGComputeContext", esgException);
                throw new ESGComputeException("Failed to create the ESGComputeContext", esgException);
            }
            catch (Exception exception)
            {
                _logging?.Invoke("Failed to create the ESGComputeContext", exception);
                throw;
            }
        }

        ~ComputationEngine()
        {
            Dispose(false);
        }

        public static ComputationEngine Instance(Action<string, Exception> logging = null)
        {
            _logging = logging;
            return InstanceLazy.Value;
        }

        public void SetConnectionString(string connectionString)
        {
            _connectionString = connectionString;
        }

        public void SetActive(Guid setId, bool isActive)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                _esgSetList.SetActive(setId, isActive, typeof(PeerGroupSet));
                _esgSetList.SetActive(setId, isActive, typeof(IndustryGroupSet));
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        private void LoadCompaniesNames(ICollection<ESGCompanyEntity> companies)
        {
            var companyIds = companies.Select(x => x.Id).ToList();

            var collection = MongoFactory.GetCollectionFromDatabase<CorporateData>(_connectionString);

            var corporateData = collection.Find(cd => companyIds.Contains(cd.Id))
                .Project(x => new { CompanyId = x.Id, x.CompanyName })
                .ToEnumerable()
                .ToDictionary(k => k.CompanyId, v => v.CompanyName);

            foreach (var company in companies)
            {
                company.Name = corporateData.ContainsKey(company.Id)
                    ? corporateData[company.Id]
                    : string.Empty;
            }
        }

        /// <exception cref="UnknownSetException">The set is not found in list.</exception>
        public Guid AddESGSet(Guid? setId, ICollection<ESGCompanyEntity> companies, 
            ICollection<IndicatorTemplate> indicatorTemplates,
            ICollection<CompanyScores> companiesScores, 
            ICollection<WeightMatrix> matrices, 
            ICollection<ReferenceUniverse> referenceUniverses, 
            Guid? defaultMatrixId = null, bool isActive = false)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                LoadCompaniesNames(companies);

                var id = AddESGSet(setId, companies, indicatorTemplates, isActive, defaultMatrixId);

                SetIndicatorsData(id, companiesScores as ICollection<CompanyIndicators<IndicatorScore>>);

                SetCompanyScores(id, companiesScores);

                AddMatrices(id, matrices);

                AddUniverses(id, referenceUniverses);

                return id;
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        private void SetCompanyScores(Guid id, ICollection<CompanyScores> companiesScores)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                ICollection<IComputationSet> sets = _esgSetList.GetSetByIdOrDefault(id);

                foreach (var set in sets.OfType<ESGSet>())
                {
                    set.AddCompaniesScores(companiesScores);
                }
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        private Guid AddESGSet(Guid? setId, ICollection<ESGCompanyEntity> companies, 
            ICollection<IndicatorTemplate> indicatorTemplates, bool isActive, Guid? defaultMatrixId)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                var factory = new ESGIndicatorConfigurationFactory();
                IndicatorsConfiguration configuration = factory.GetBuilder().GetConfiguration(indicatorTemplates);

                var id = AddESGSet(setId, companies, configuration, isActive, defaultMatrixId);
                return id;
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        /// <exception cref="DuplicateEntityException">Adding an already added matrix.</exception>
        /// <exception cref="UnknownCompanyException">
        /// The matrix contains informationa about a company that was not registred or inssuficient
        /// data provided .
        /// </exception>
        /// <exception cref="UnknownIndicatorException">
        /// Matrix contains information about an indicator not present in the IndicatorTemplates set.
        /// </exception>
        /// <exception cref="UnknownMatrixException">When matrix id is not attached to set.</exception>
        public Guid AddGovernanceSet(Guid? setId, ICollection<GovernanceCompanyEntity> companies, 
            ICollection<IndicatorTemplate> indicatorTemplates,
            IEnumerable<CompanyIndicators<IndicatorScore>> companiesRawScores, RegionMatrix matrix, 
            ICollection<CombinationModifier> modifiers)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                var factory = new ESGIndicatorConfigurationFactory();
                IndicatorsConfiguration configuration = factory.GetBuilder().GetConfiguration(indicatorTemplates);

                GovernanceSet set = AddGovernanceSet(ref setId, companies, configuration);
                set.AddIndicatorsRawScore(companiesRawScores);
                set.AddMatrix(matrix.MatrixId, matrix.Matrix);
                set.AddCombinationModifiers(matrix.MatrixId, modifiers);
                set.AddIssueWeights(matrix.MatrixId, matrix.IssuesWeights);
                return setId.GetValueOrDefault();
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        public void AddMatrices(Guid setId, ICollection<WeightMatrix> matrices)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                ICollection<IComputationSet> sets;
                if (!_esgSetList.GetSetByGuid(setId, out sets))
                {
                    throw new UnknownSetException(setId);
                }


                foreach (var set in sets)
                {

                    foreach (var matrix in matrices)
                    {
                        set.AddMatrix(matrix.MatrixId, matrix.CompaniesWeights);
                    }

                }
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        /// <exception cref="UnknownSetException">The set is not found in list.</exception>
        public void AddUniverses(Guid setId, ICollection<ReferenceUniverse> universes)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                ICollection<IComputationSet> sets;
                if (!_esgSetList.GetSetByGuid(setId, out sets))
                {
                    throw new UnknownSetException(setId);
                }

                foreach (var set in sets.OfType<ESGSet>())
                {
                    foreach (var universe in universes)
                    {
                        set.AddUniverse(universe);
                    }
                }
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        /// <exception cref="SynchronizationLockException">
        /// The current thread has not entered the lock in read mode.
        /// </exception>
        /// <exception cref="EmptySetListException">Computation engine not populated.</exception>
        /// <exception cref="UnknownSetException">Set not found.</exception>
        /// <exception cref="LockRecursionException">
        /// The <see cref="P:System.Threading.ReaderWriterLockSlim.RecursionPolicy"/> property is
        /// <see cref="F:System.Threading.LockRecursionPolicy.NoRecursion"/> and the current thread
        /// has already entered read mode. -or-The recursion number would exceed the capacity of the
        /// counter. This limit is so large that applications should never encounter it.
        /// </exception>
        //public ESGComputeResult ComputeESGRatings(Guid? setId, Guid matrixId, Guid refUnivId, Entities.ESGRatings.ComparativeGroupEnum groupBy, ESGRatingsOptions.CompanyAttributesEnum filter = 0, ESGRatingsOptions.IncludeFlags options = ESGRatingsOptions.IncludeFlags.IncludeAggregatedScores)
        public ESGRatingsDto ComputeESGRatings(ESGRatingsOptionsDto options)
        {
            _rwlSyncObject.EnterReadLock();

            try
            {
                if (_esgSetList.IsNullOrEmpty())
                {
                    throw new EmptySetListException();
                }

                ESGSet set;


                if (options.SetId.HasValue && options.SetId.Value != default(Guid))
                {

                    set = _esgSetList.GetSetByIdOrDefault(options.SetId, options.GroupBy.ToESGSetType()) as ESGSet;
                }
                else
                {
                    var kvpSet = _esgSetList.GetSetByMatrixAndReferenceUniverse(options.MatrixId, options.RefUnivId, options.GroupBy.ToESGSetType());
                    if (kvpSet.Key == default(Guid))
                    {
                        throw new UnknownSetException(
                            $"UnknownSet for matrix {options.MatrixId} and referenceUniverse {options.RefUnivId}");
                    }
                    set = kvpSet.Value;
                    options.SetId = kvpSet.Key;
                }

                if (set != null && !set.GetLoadedMatrices().Contains(options.MatrixId))
                {
                    throw new UnknownMatrixException(options.MatrixId);
                }

                if (set != null && !set.GetLoadedUniverses().Contains(options.RefUnivId))
                {
                    throw new UnknownUniverseException(options.RefUnivId);
                }

                return GetRatings(options, set);
            }
            catch (ESGException exception)
            {
                _logging?.Invoke("", exception);
                throw new ComputeException(exception.Message, exception);
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        /// <exception cref="UnknownMatrixException">
        /// Matrix provided in options doesn't match the one attached to the set .
        /// </exception>
        /// <exception cref="ComputeException">CUDA set not found.</exception>
        public GovernanceComputeResult ComputeGovernanceRatings(Guid setId, Guid matrixId, bool includeWeightedScores, bool includeWeights)
        {
            _rwlSyncObject.EnterReadLock();
            try
            {
                IComputationSet set;
                _esgSetList.GetSetByGuid(setId, typeof(GovernanceSet), out set);

                var governanceSet = (GovernanceSet)set;
                GovernanceComputeResult result = governanceSet.Ratings(matrixId, includeWeightedScores, includeWeights);
                result.SetId = setId;
                return result;
            }
            catch (ESGException exception)
            {
                _logging?.Invoke("", exception);
                throw new ComputeException(exception.Message, exception);
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        public IEnumerable<Guid> DeleteAll()
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                var deletedSetGuids = _esgSetList.GetSetIds().Select(x => x.SetId).ToList();

                _esgSetList.Delete(deletedSetGuids);

                return deletedSetGuids;
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        public IEnumerable<Guid> DeleteESGSets()
        {
            return DeleteSets<ESGSet>();
        }

        public IEnumerable<Guid> DeleteGovernanceSets()
        {
            return DeleteSets<GovernanceSet>();
        }

        public bool DeleteMatrix(Guid setId, Guid matrixId)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                IComputationSet industryGroupSet;
                IComputationSet peerGroupSet;

                var found = _esgSetList.GetSetByGuid(setId, typeof(IndustryGroupSet), out industryGroupSet);
                bool deleted = false;

                if (found)
                {
                    deleted = industryGroupSet.DeleteMatrix(matrixId);
                }

                found = _esgSetList.GetSetByGuid(setId, typeof(PeerGroupSet), out peerGroupSet);

                if (found)
                {
                    deleted = deleted && peerGroupSet.DeleteMatrix(matrixId);
                }

                return deleted;
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        /// <exception cref="UnknownSetException">The set is not found in list.</exception>
        /// <exception cref="ComputeException">setId</exception>
        public bool DeleteReferenceUniverse(Guid setId, Guid referenceUniverseId)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                var setList = _esgSetList.GetSetByIdOrDefault(setId);

                return setList != null && setList.OfType<ESGSet>().Aggregate(false, (current, esgSet) => current && esgSet.DeleteReferenceUniverse(referenceUniverseId));
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        /// <exception cref="UnknownSetException">Set not found.</exception>
        public bool DeleteSet(Guid id)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                if (id.Equals(Guid.Empty))
                {
                    throw new UnknownSetException(id);
                }

                _esgSetList.Delete(id);

                return true;
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private IEnumerable<Guid> GetCompaniesInComparativeGroup(Guid companyId, Guid matrixId, Guid referenceUniverseId, Entities.ESGRatings.ComparativeGroupEnum groupBy, Guid? setId = null)
        {
            _rwlSyncObject.EnterReadLock();
            try
            {
                var set = setId.HasValue ? _esgSetList.GetSetByIdOrDefault(setId.Value, groupBy.ToESGSetType()) as ESGSet : _esgSetList.GetSetByMatrixAndReferenceUniverse(matrixId, referenceUniverseId, groupBy.ToESGSetType()).Value;
                return set?.GetCompaniesInGroup(companyId);
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        private IEnumerable<Guid> GetMatchingCompanies(Guid matrixId, Guid referenceUniverseId, List<string> peerGroups, List<string> industryGroups, Guid? setId = null)
        {
            _rwlSyncObject.EnterReadLock();
            try
            {
                var industryGroupSet = setId.HasValue ? _esgSetList.GetSetByIdOrDefault(setId.Value, typeof(IndustryGroupSet)) as ESGSet : _esgSetList.GetSetByMatrixAndReferenceUniverse(matrixId, referenceUniverseId, typeof(IndustryGroupSet)).Value;
                var companiesInIndustryGroups = industryGroupSet?.GetCompaniesInGroup(industryGroups, x => x.IndustryGroup);
                return companiesInIndustryGroups;
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        public IEnumerable<Guid> GetMatrices(Guid? setId)
        {
            _rwlSyncObject.EnterReadLock();
            try
            {
                return GetItems(setId, x => x.GetLoadedMatrices());
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        public List<MemorySizing> GetMemoryStatistics()
        {
            _rwlSyncObject.EnterReadLock();
            try
            {
                return new List<MemorySizing>();
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        public IEnumerable<Guid> GetUniverses(Guid? setId)
        {
            _rwlSyncObject.EnterReadLock();
            try
            {
                return GetItems(setId, x =>
                {
                    var esgSet = x as ESGSet;
                    return esgSet?.GetLoadedUniverses();
                });
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        //toDO: refactor this

        public ICollection<Tuple<Guid, bool>> ReadSets(Guid? setId, Entities.ESGRatings.ComparativeGroupEnum? comparativeGroup = null)
        {

            _rwlSyncObject.EnterReadLock();

            IEnumerable<Guid> ids;
            var data = new List<Tuple<Guid, bool>>();
            ICollection<IComputationSet> setList;


            try
            {
                var internalType = comparativeGroup.HasValue ? comparativeGroup.Value.ToESGSetType() : typeof(GovernanceSet);

                if (setId.HasValue)
                {
                    ids = new List<Guid> { setId.Value };

                }
                else
                {
                    var tuples = _esgSetList.GetSetIds();
                    ids = tuples.Where(x => x.ComparativeGroupType == internalType).Select(x => x.SetId);
                }
                foreach (var id in ids)
                {
                    _esgSetList.GetSetByGuid(id, out setList);
                    if (setList != null && setList.Any(x => x.GetType() == internalType))
                    {
                        var sets = setList.Where(x => x.GetType() == internalType);
                        data.AddRange(sets.Select(x => new Tuple<Guid, bool>(id, x.IsActive)));
                    }
                }
                return data;
            }
            finally
            {
                _rwlSyncObject.ExitReadLock();
            }
        }

        public void SetIndicatorsData(Guid setId, ICollection<CompanyIndicators<IndicatorScore>> companiesIndicators)
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                var sets = _esgSetList.GetSetByIdOrDefault(setId);

                foreach (var set in sets)
                {
                    set.AddIndicatorsRawScore(companiesIndicators);
                }
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        //TODO: redo in esgSet
        public void SetIndicatorTemplates(Guid setId, IEnumerable<IndicatorTemplate> indicatorTemplates)
        {
            _rwlSyncObject.EnterWriteLock();

            try
            {
                var sets = _esgSetList.GetSetByIdOrDefault(setId);
                //peerGroupSet.SetIndicatorConfiguration();
                //industryGroupSet.SetIndicatorConfiguration();
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        private IIndex<Guid, string> GetComparativeGroup(ESGSet set)
        {
            var comparativeGroupIndex = 0;
            IIndex<Guid, string> comparativeGroup;
            if (!set.ComparativeGroups.TryGetValue(comparativeGroupIndex, out comparativeGroup))
            {
                throw new ESGEntityNotFound("Comparative groups is missing");
            }

            return comparativeGroup;
        }

        private RatingsResult LoadRatings(ESGRatingsOptionsDto esgRatingsOptions, ESGSet set)
        {
            var options = esgRatingsOptions.ToDomainModel();

            var includeIndicatorRatings = ((options.Include & ESGRatingsOptions.IncludeFlags.IncludeIndWeightedScores) != 0); // && options.MatrixId != set.DefaultMatrixId;
            var includeAggregatedWeights = ((options.Include & ESGRatingsOptions.IncludeFlags.IncludeAggregatesWeights) != 0);
            var includeWeights = ((options.Include & ESGRatingsOptions.IncludeFlags.IncludeWeights) != 0); // && options.MatrixId != set.DefaultMatrixId;
            var isDefaultWeightMatrix = options.MatrixId == set.DefaultMatrixId;

            var comparativeGroup = GetComparativeGroup(set);
            int[] groupByHeads = comparativeGroup.GroupByHeads;
            var clusterGroupsCount = groupByHeads.Length + 1; //TODO: review here if correct

            RatingsResult ratings = new RatingsResult(new BufferManager(),
                set.CompaniesNo,
                set.IndicatorsNo,
                set.IndicatorsConfiguration.ClusterCount,
                clusterGroupsCount,
                includeIndicatorRatings,
                includeAggregatedWeights,
                includeWeights,
                isDefaultWeightMatrix);

                ratings.AllocateBuffers();

            if (set.CompaniesNo > 0)
            {
                var companyIds = comparativeGroup.GetReverseIndexCopy();


                for (var clusterIndex = 0; clusterIndex < set.IndicatorsConfiguration.ClusterCount; clusterIndex++)
                {
                    for (var companyIndex = 0; companyIndex < set.CompaniesNo; companyIndex++)
                    {
                        var index = clusterIndex * set.CompaniesNo + companyIndex;

                        ratings.CompanyIndices[index] = companyIndex;

                        var companyId = companyIds[companyIndex];

                        ratings.Ratings[index] = set.CompaniesScores[companyId].CompanyRatings[clusterIndex].Score;

                        if (includeAggregatedWeights)
                        {
                            ratings.AggregatedWeights[index] = set.CompaniesScores[companyId].CompanyRatings[clusterIndex].Weight;
                        }
                    }
                }
            }

            return ratings;
        }


        private ESGRatingsDto GetRatings(ESGRatingsOptionsDto esgRatingsOptions, ESGSet set)
        {

            var options = esgRatingsOptions.ToDomainModel();

            WeightMatrixStruct matrixInfo;
            if (!set.Matrices.TryGetValue(options.MatrixId, out matrixInfo))
            {
                throw new UnknownMatrixException(options.MatrixId);
            }

            int internalUniverseId;
            if (!set.Universes.TryGetValue(options.RefUnivId, out internalUniverseId))
            {
                throw new UnknownUniverseException(options.RefUnivId);
            }

            var comparativeGroup = GetComparativeGroup(set);
            int[] groupByHeads = comparativeGroup.GroupByHeads;
            var filter = (byte)options.ComputationCompanyAttributes;

            var ratings = LoadRatings(esgRatingsOptions, set);


            var esgComputeResult = new ESGComputeResult
            {
                SetId = options.SetId.Value,
                CompaniesHeadsGroups = (int[])groupByHeads.Clone(),
                CompaniesMap = comparativeGroup.GetReverseIndexCopy(),
                CompaniesNames = set.Companies.ToDictionary(k => k.Key, v => v.Value.Name),
                Configuration = set.IndicatorsConfiguration,
                Ratings = ratings,
                IndicatorsScores = set.IndicatorScores,
                MatrixInfo = matrixInfo,
                ComputeOptions = options.Include,
                IsDefaultWeightMatrix = options.MatrixId == set.DefaultMatrixId
            };


            if (esgComputeResult.IsDefaultWeightMatrix)
            {
                IsInReferenceUniverse(ratings, options, set, filter);

                return GetComputationResult(esgComputeResult, esgRatingsOptions, set);
            }


            byte[] flags = set.EncodedUniverses[internalUniverseId];
            byte[] attributes = set.CompanyAttributes;

            var includeAggregatedWeights = ((options.Include & ESGRatingsOptions.IncludeFlags.IncludeAggregatesWeights) != 0);
            var includeWeights = ((options.Include & ESGRatingsOptions.IncludeFlags.IncludeWeights) != 0); // && options.MatrixId != set.DefaultMatrixId;



            if (set.CompaniesNo > 0)
            {
                byte[] clusterStarts = set.IndicatorsConfiguration.DisjunctClustersHeads; // clusters interval (8 + 1 intervals)
                byte[] clusterGroups = set.IndicatorsConfiguration.DisjunctClustersMap;


                var companyIds = comparativeGroup.GetReverseIndexCopy();

                for (int companyIndex = 0; companyIndex < set.CompaniesNo; companyIndex++)
                {
                    var companyId = companyIds[companyIndex];

                    var indicators = set.IndicatorScores[companyId];


                    float filterSign = -1;
                    float filterDiff = -4;

                    var flag = flags[companyIndex];
                    var attribute = attributes[companyIndex];
 
                    //if (flags[line] * (((attributes[line] & filter) == filter) || (!filter)))
                    if (flag != 0 && (((attribute & filter) == filter) || (filter != 0)))
                    {
                        filterSign = 1;
                        filterDiff = 0;
                    }
                    else
                    {

                    }


                    float clusterGroupScore = 0;
                    float clusterGroupWeight = 0;
                    byte currentClusterGroup = 0;

                    float groupRelativeWeight = 0;
                    float groupScore = 0;
                    byte currentGroup = 0;

                    float score = 0;
                    float weight = 0;

                    float[] esgScore = new float[set.IndicatorsConfiguration.EsgMap.Count];
                    float[] esgWeight = new float[set.IndicatorsConfiguration.EsgMap.Count];
                    float[] clusterGroupsResults = new float[set.IndicatorsConfiguration.ClusterCount];
                    float[] clusterGroupsWeights = new float[set.IndicatorsConfiguration.ClusterCount];

                    for (int i = 0; i < set.IndicatorsNo; i++)
                    {
                        if (currentGroup < set.IndicatorsConfiguration.ESGHeads.Length && i == set.IndicatorsConfiguration.ESGHeads[currentGroup])
                        {
                            esgScore[currentGroup] = conditionalDiv(filterSign, filterDiff, groupScore, groupRelativeWeight);
                            esgWeight[currentGroup] = groupRelativeWeight;

                            score += groupScore;
                            weight += groupRelativeWeight;

                            currentGroup++;

                            groupRelativeWeight = 0;
                            groupScore = 0;
                        }

                        if (currentClusterGroup < set.IndicatorsConfiguration.DisjunctClustersHeads.Length && 
                            i == set.IndicatorsConfiguration.DisjunctClustersHeads[currentClusterGroup])
                        {
                            clusterGroupsResults[clusterGroups[currentClusterGroup]] = clusterGroupScore + clusterGroupsResults[clusterGroups[currentClusterGroup]];
                            clusterGroupsWeights[clusterGroups[currentClusterGroup]] = clusterGroupWeight + clusterGroupsWeights[clusterGroups[currentClusterGroup]];

                            currentClusterGroup++;

                            clusterGroupScore = 0;
                            clusterGroupWeight = 0;
                        }

                        float indicatorRating = indicators[i].Score;
                        if (indicators[i].Weight != null)
                            indicatorRating = indicators[i].Score * indicators[i].Weight.Value;


                        var index = companyIndex * set.IndicatorsNo + i;

                        ratings.IndicatorsRatings[index] = indicatorRating;

                        groupScore += indicatorRating;
                        clusterGroupScore += indicatorRating;

                        if (indicators[i].Weight != null)
                        {
                            groupRelativeWeight += (float)indicators[i].Weight;
                            clusterGroupWeight += (float)indicators[i].Weight;
                        }
                    }

                    clusterGroupsResults[clusterGroups[currentClusterGroup]] = clusterGroupScore + clusterGroupsResults[clusterGroups[currentClusterGroup]];
                    clusterGroupsWeights[clusterGroups[currentClusterGroup]] = clusterGroupWeight + clusterGroupsWeights[clusterGroups[currentClusterGroup]];

                    esgScore[currentGroup] = conditionalDiv(filterSign, filterDiff, groupScore, groupRelativeWeight);
                    esgWeight[currentGroup] = groupRelativeWeight;

                    score += groupScore;
                    weight += groupRelativeWeight;


                    ratings.Ratings[companyIndex] = conditionalReturn(filterSign, filterDiff, score);

                    if (includeWeights)
                    {
                        ratings.AggregatedWeights[companyIndex] = weight;
                    }

                    for (int i = 0; i < set.IndicatorsConfiguration.DisjunctClustersCount; i++)
                    {
                        var index = (4 + i) * set.CompaniesNo + companyIndex;

                        ratings.Ratings[index] = conditionalReturn(filterSign, filterDiff, clusterGroupsResults[i]);

                        if (includeAggregatedWeights)
                        {
                            ratings.AggregatedWeights[index] = clusterGroupsWeights[i];
                        }
                    }

                    for (int i = 0; i < set.IndicatorsConfiguration.ClusterCount; i++)
                    {
                        var index = i * set.CompaniesNo + companyIndex;

                        ratings.CompanyIndices[index] = companyIndex;
                    }

                    for (int i = 0; i < set.IndicatorsConfiguration.EsgMap.Count; i++)
                    {
                        var index = companyIndex + (i + 1) * set.CompaniesNo;

                        ratings.Ratings[index] = esgScore[i];

                        if (includeAggregatedWeights)
                        {
                            ratings.AggregatedWeights[index] = esgWeight[i];
                        }
                    }


                }

                IsInReferenceUniverse(ratings, options, set, filter);
            }

            return GetComputationResult(esgComputeResult, esgRatingsOptions, set);
        }

        private void IsInReferenceUniverse(RatingsResult ratingsResult, ESGRatingsOptions options, ESGSet set, byte filter)
        {
            int internalUniverseId;

            if (!set.Universes.TryGetValue(options.RefUnivId, out internalUniverseId))
            {
                throw new UnknownUniverseException(options.RefUnivId);
            }

            byte[] universe = set.EncodedUniverses[internalUniverseId];
            int[] companiesMap = ratingsResult.CompanyIndices;
            var companiesCount = ratingsResult.CompaniesCount;

            for (int i = 0; i <= companiesCount; i++)
            {
                //if (universe[companiesMap[i]] == 1 && ratings[i] >= -3
                //    && ((hostAttributes[companiesMap[i]] & filter) == filter || (!filter)))

                if (universe[companiesMap[i]] == 1 && ratingsResult.Ratings[i] >= -3
                    && ((set.CompanyAttributes[companiesMap[i]] & filter) == filter || (filter != 0)))
                {

                    ratingsResult.IsInReferenceUniverse[companiesMap[i]] = true;
                }
                else
                {
                    ratingsResult.IsInReferenceUniverse[companiesMap[i]] = false;
                }
            }
        }




        private ICompanyFilter _companyfilter;
        private Guid[] _companiesMap;
        private Dictionary<Guid, string> _companiesNames;

        private ESGRatingsDto GetComputationResult(ESGComputeResult result, ESGRatingsOptionsDto options, ESGSet set)
        {
            var esgRatings = new ESGRatingsDto
            {
                SetId = result.SetId,
            };

            _companyfilter = GetFilter(options);
            _companiesNames = result.CompaniesNames;
            _companiesMap = result.CompaniesMap;

            esgRatings.IndicatorsGroups = GetIndicatorsGroups(result);

            if (options.IncludeIndWeightedScores)
            {
                esgRatings.IndicatorsCodes = result.Configuration.Codes.ToArray();
                esgRatings.Indicators = GetIndicators(result).ToList();
            }

            var ratings = GetRatings(result).ToList();

            //var list = ratings
            //    .Select(x => new { x.ClustersAggregates[0].Rank, Score = x.ClustersRatings[0] })
            //    .OrderBy(x => x.Rank)
            //    .ToList();


            if (options.IncludeRatings)
            {
                esgRatings.Ratings = ratings;
            }

            if (options.IncludeAggregatedScores)
            {
                options.IncludeTopPerformersInfo = true;
                var groupsTopCompanies = GetTopCompaniesScoresOnComparativeGroup(result, ratings);

                esgRatings.Groups = GetGroups(result, options.IncludeTopPerformersInfo, groupsTopCompanies).ToArray();
            }

            //if (options.IncludeAggregatedScores)
            //{
            //    esgRatings.Groups = GetGroupsWithTopCompanies(result, ratings, options.IncludeTopPerformersInfo).ToArray();
            //}


            return esgRatings;
        }




        private ICompanyFilter GetFilter(ESGRatingsOptionsDto options)
        {
            if (options.CompaniesIds != null && options.CompaniesIds.Count > 0)
            {
                var additionalCompaniesIds = new List<Guid>();

                if (options.IncludeCompaniesInGroup)
                {
                    foreach (var companyId in options.CompaniesIds)
                    {
                        var companiesToAdd = GetCompaniesInComparativeGroup(
                            companyId,
                            options.MatrixId,
                            options.RefUnivId,
                            options.GroupBy,
                            options.SetId);

                        if (companiesToAdd != null)
                        {
                            additionalCompaniesIds.AddRange(companiesToAdd);
                        }
                    }
                }

                additionalCompaniesIds.AddRange(GetMatchingCompanies(
                    options.MatrixId, options.RefUnivId,
                    options.PeerGroups, options.IndustryGroups, options.SetId));

                return new CompanyFilter(options.CompaniesIds.Union(additionalCompaniesIds));
            }

            if (options.IndustryGroups == null || options.IndustryGroups.Count == 0)
            {
                if ((options.PeerGroups == null) || options.PeerGroups.Count == 0)
                {
                    return null;
                }
            }

            return new CompanyFilter(GetMatchingCompanies(
                options.MatrixId, options.RefUnivId, 
                options.PeerGroups, options.IndustryGroups, options.SetId));
        }

        public IndicatorsGroupsDto GetIndicatorsGroups(ESGComputeResult result)
        {
            var indicatorsGroups = new IndicatorsGroupsDto
            {
                All = new ClusterDto { Id = 0, Name = "All" },
                Themes = new List<ClusterDto>(),
                Clusters = new List<ClusterDto>()
            };

            var index = 1;
            foreach (var themeMap in result.Configuration.EsgMap)
            {
                indicatorsGroups.Themes.Add(new ClusterDto { Id = index, Name = themeMap });
                index++;
            }

            foreach (var themeMap in result.Configuration.ClustersMap)
            {
                indicatorsGroups.Clusters.Add(new ClusterDto { Id = index, Name = themeMap });
                index++;
            }

            return indicatorsGroups;
        }

        public IEnumerable<CompanyIdicatorsDto> GetIndicators(ESGComputeResult result)
        {
            if (result.Ratings == null)
            {
                yield break;
            }

            var companiesCount = result.Ratings.CompaniesCount;
            var indicatorsCount = result.Ratings.IndicatorsCount;

            for (var i = 0; i < companiesCount; i++)
            {
                if (!IncludeByIndex(i))
                {
                    continue;
                }

                var companyId = result.CompaniesMap[i];

                var companyIndicators = new List<IndicatorScoreStructDto>();

                for (var j = 0; j < indicatorsCount; j++)
                {
                    var indicatorData = GetIndicatorScoresStruct(result, companyId, j);

                    var score = indicatorData.Score;
                    if (Math.Abs(score - (-1)) < 1e-10)
                    {
                        continue;
                    }

                    var index = j * companiesCount + i;

                    if (result.MatrixInfo.IsNaN[index])
                    {
                        continue;
                    }

                    var weightedScore = result.IsDefaultWeightMatrix 
                        ? indicatorData.WeightedScore.GetValueOrDefault() 
                        : result.Ratings.IndicatorsRatings[index];

                    var weight = (result.ComputeOptions & ESGRatingsOptions.IncludeFlags.IncludeWeights) != 0 
                        ? result.IsDefaultWeightMatrix 
                            ? indicatorData.Weight.GetValueOrDefault() 
                            : result.Ratings.Weights[index] 
                        : 0;

                    companyIndicators.Add(
                        new IndicatorScoreStructDto
                        {
                            Index = j,
                            Score = score,
                            WeightedScore = weightedScore,
                            Weight = weight
                        });
                }

                yield return new CompanyIdicatorsDto { CompanyId = companyId, Indicators = companyIndicators };
            }
        }

        private IndicatorDataStruct GetIndicatorScoresStruct(ESGComputeResult result, Guid companyId, int indicatorIndex)
        {
            try
            {
                IndicatorDataStruct[] indicatorsData;

                if (result.IndicatorsScores.TryGetValue(companyId, out indicatorsData))
                {
                    var indicator = indicatorsData[indicatorIndex];
                    if (indicator.IsNotNan == 0)
                    {
                        return IndicatorDataStruct.Default();
                    }

                    return indicator;
                }
            }
            catch (ArgumentOutOfRangeException)
            {
                return IndicatorDataStruct.Default();
            }

            return IndicatorDataStruct.Default();
        }

        private bool IncludeById(Guid companyId)
        {
            return _companyfilter == null || _companyfilter.IsMatch(companyId);
        }

        private bool IncludeByIndex(int companyIndex)
        {
            return IncludeById(_companiesMap[companyIndex]);
        }

        private Guid GetCompanyId(int index)
        {
            return _companiesMap[index];
        }

        private string GetCompanyName(Guid companyId)
        {
            return _companiesNames[companyId];
        }

        private List<KeyValuePair<int, TopCompaniesInfo>> _groupTopCompanies;

        public List<CompanyRatingDto> GetRatings(ESGComputeResult result)
        {
            var collection = new List<CompanyRatingDto>();

            if (result.Ratings == null)
            {
                return collection;
            }

            if ((result.ComputeOptions & ESGRatingsOptions.IncludeFlags.IncludeRatings) == 0)
            {
                return collection;
            }

            var companiesCount = result.Ratings.CompaniesCount;
            var clustersCount = result.Ratings.ClustersCount;
            var ratings = result.Ratings;


            _groupTopCompanies = new List<KeyValuePair<int, TopCompaniesInfo>>();

            var companiesRatings = ComputeCompaniesRatings(result);


            for (var i = 0; i < companiesCount; i++)
            {
                var companyIndex = ratings.CompanyIndices[i];

                if (IncludeByIndex(companyIndex))
                {
                    collection.Add(companiesRatings[companyIndex]);
                }
            }

            // compute ranks;

            var groupCompanies = collection.GroupBy(x => x.GroupId).ToDictionary(x => x.Key);

            var clusterRanks = new Dictionary<KeyValuePair<int, int>, Dictionary<Guid, int>>();
            var clusterGroupsCount = result.Ratings.ClusterGroupsCount;

            for (var cluster = 0; cluster < clustersCount; cluster++)
            {
                for (var groupId = 0; groupId < clusterGroupsCount; groupId++)
                {
                    if (groupCompanies.ContainsKey(groupId))
                    {
                        var companies = groupCompanies[groupId].ToList();

                        var key = new KeyValuePair<int, int>(cluster, groupId);

                        clusterRanks.Add(key, GetRanks(companies, x => x.CompanyId, x => x.ClustersRatings[cluster]));
                    }
                }
            }

            foreach (var item in collection)
            {
                var clustersAggregates = new List<CompanyGroupValuesDto>();

                var count = groupCompanies[item.GroupId].Count();

                for (var cluster = 0; cluster < clustersCount; cluster++)
                {
                    var key = new KeyValuePair<int, int>(cluster, item.GroupId);

                    var rank = clusterRanks[key].ContainsKey(item.CompanyId)
                        ? clusterRanks[key][item.CompanyId]
                        : -1;

                    var percentile = ComputePercentile(count, rank);

                    clustersAggregates.Add(new CompanyGroupValuesDto(rank, percentile, count));
                }

                item.ClustersAggregates = clustersAggregates;
            }

            return collection;
        }

        private Dictionary<Guid, int> GetRanks(List<CompanyRatingDto> companyRatings, Func<CompanyRatingDto, Guid> keySelector, Func<CompanyRatingDto, double?> scoreSelector)
        {
            int rank = 1;
            int itemsWithSameRank = 1;
            bool first = true;
            CompanyRatingDto previousItem = null;

            IComparer<double?> scoreComparer = new DoubleComparer();

            var orderedData = companyRatings.OrderByDescending(scoreSelector, scoreComparer);

            var ranks = new Dictionary<Guid, int>();

            foreach (var item in orderedData)
            {
                if (!first)
                {
                    if ((scoreComparer.Compare(scoreSelector(previousItem), scoreSelector(item)) == 0))
                    {
                        itemsWithSameRank++;
                    }
                    else
                    {
                        rank += itemsWithSameRank;
                        itemsWithSameRank = 1;
                    }
                }

                first = false;

                var key = keySelector(item);

                ranks.Add(key, rank);

                previousItem = item;
            }

            return ranks;
        }

        private class DoubleComparer : IComparer<double?>
        {
            private const double difference = 1e-16;
            public int Compare(double? x, double? y)
            {
                var dif = (x.GetValueOrDefault() - y.GetValueOrDefault());

                if (Math.Abs(dif) <= difference)
                    return 0;

                if (dif > difference)
                    return 1;

                return -1;

            }
        }

        private int GetGroupId(ESGComputeResult result, int companyIndex)
        {
            var heads = result.CompaniesHeadsGroups;
            var headsCount = heads.Length;
            for (var i = 0; i < headsCount; i++)
            {
                if (heads[i] > companyIndex)
                {
                    return i;
                }
            }

            return headsCount;
        }

        private bool CompanyHasPeersInSelection(ESGComputeResult result, int companyId, int ratingIndex)
        {
            return !(result.Ratings.IsInReferenceUniverse[companyId]
                && result.Ratings.Group_Counts[ratingIndex] == 1
                && result.Ratings.Rankings[ratingIndex] == 1);
        }

        private Dictionary<int, CompanyRatingDto> ComputeCompaniesRatings(ESGComputeResult result)
        {
            var ratings = result.Ratings;
            var companiesCount = result.Ratings.CompaniesCount;
            var clustersCount = result.Ratings.ClustersCount;

            var companiesRatings = (from ci in Enumerable.Range(0, companiesCount)
                      let companyIndex = ratings.CompanyIndices[ci]
                      select
                          new KeyValuePair<int, CompanyRatingDto>(
                          companyIndex,
                          new CompanyRatingDto { CompanyId = GetCompanyId(companyIndex), CompanyName = GetCompanyName(GetCompanyId(companyIndex)) })).ToDictionary(
                              c => c.Key,
                              c => c.Value);

            for (var j = 0; j < clustersCount; j++)
            {
                for (var i = 0; i < companiesCount; i++)
                {
                    var ratingIndex = j * companiesCount + i;
                    var companyId = ratings.CompanyIndices[ratingIndex];

                    var orderedCompanyRating = companiesRatings[companyId];
                    var unorderedCompanyRating = companiesRatings[i];

                    ComputeCompanyRatings(result, orderedCompanyRating, unorderedCompanyRating, companyId, ratingIndex, j);
                }
            }

            return companiesRatings;
        }

        private void ComputeCompanyRatings(ESGComputeResult result, 
            CompanyRatingDto orderedCompanyRating, CompanyRatingDto unorderedCompanyRating,
            int companyId, int ratingIndex, int clusterIndex)
        {
            var top = 5;

            orderedCompanyRating.ClustersRatings.Add(result.Ratings.Ratings[ratingIndex]);

            if (result.Ratings.ComputeAggregatedWeights)
            {
                unorderedCompanyRating.ClustersWeights.Add(result.Ratings.AggregatedWeights[ratingIndex]);
            }


            orderedCompanyRating.IsInReferenceUniverse = result.Ratings.IsInReferenceUniverse[companyId];

            var groupId = GetGroupId(result, companyId);
            orderedCompanyRating.GroupId = groupId;

            var companyGroupValues = CompanyHasPeersInSelection(result, companyId, ratingIndex)
                                         ? new CompanyGroupValuesDto(
                                               result.Ratings.Rankings[ratingIndex],
                                               result.Ratings.Percentile[ratingIndex],
                                               result.Ratings.Group_Counts[ratingIndex])
                                         : CompanyGroupValuesDto.Default();

            orderedCompanyRating.ClustersAggregates.Add(companyGroupValues);

            if (companyGroupValues.Rank <= top && result.Ratings.IsInReferenceUniverse[companyId])
            {
                _groupTopCompanies.Add(new KeyValuePair<int, TopCompaniesInfo>(groupId,
                        new TopCompaniesInfo(
                            companyId,
                            companyGroupValues.Rank,
                            clusterIndex,
                            result.Ratings.Ratings[ratingIndex])));
            }
        }



        private Dictionary<int, List<CompanyScoreDto>> GetTopCompaniesScoresOnComparativeGroup(ESGComputeResult result, List<CompanyRatingDto> companyRatings)
        {
            var groupsTopCompanies = new Dictionary<int, List<CompanyScoreDto>>();
            var groupsCount = result.Ratings.ClusterGroupsCount;

            foreach (var groupId in Enumerable.Range(0, groupsCount))
            {
                var groupCompanies = companyRatings.Where(x => x.GroupId == groupId).ToList();

                var topCompanies = groupCompanies
                    .Select(x => new { x.CompanyId, x.ClustersAggregates[0].Rank, Score = x.ClustersRatings[0] })
                    .OrderBy(x => x.Rank)
                    .Take(5)
                    .Select(x => new CompanyScoreDto()
                    {
                        CompanyId = x.CompanyId,
                        Rank = x.Rank,
                        Score = x.Score,
                        Index = 0
                    })
                    .ToList();

                groupsTopCompanies.Add(groupId, topCompanies);
            }

            return groupsTopCompanies;
        }

        private IEnumerable<GroupRatingDto> GetGroups(ESGComputeResult result, bool includeTopPerformersInfo, Dictionary<int, List<CompanyScoreDto>> topInGroupsDictionary)
        {
            var groupRatings = new List<GroupRatingDto>();

            if (result.Ratings == null)
            {
                return groupRatings;
            }

            int[] groupByHeads = result.CompaniesHeadsGroups; 
            var groupByHeadsCount = groupByHeads.Length;
            int nextChangeIndex = 0;
            int currentHeadIndex = -1;

            var companiesCount = result.Ratings.CompaniesCount;
            var clustersCount = result.Ratings.ClustersCount;
            var ratings = result.Ratings;


            var companiesRatings = ComputeCompaniesRatings(result);
            var groupCompanies = new Dictionary<int, List<CompanyRatingDto>>();
            var goupCompanyRatings = new List<CompanyRatingDto>();


            for (int i = 0; i <= companiesCount; i++)
            {
                if (i == nextChangeIndex) //head group change
                {
                    if (i != 0)
                    {
                        groupCompanies.Add(currentHeadIndex, goupCompanyRatings);
                        goupCompanyRatings = new List<CompanyRatingDto>();
                    }

                    if (i == companiesCount)
                    {
                        break;
                    }

                    nextChangeIndex = companiesCount;

                    currentHeadIndex++;

                    if (currentHeadIndex < groupByHeadsCount)
                    {
                        nextChangeIndex = groupByHeads[currentHeadIndex];
                    }
                }

                var companyIndex = ratings.CompanyIndices[i];

                goupCompanyRatings.Add(companiesRatings[companyIndex]);
            }


            foreach (var group in groupCompanies)
            {
                GroupRatingDto groupRating = new GroupRatingDto
                {
                    GroupId = group.Key,
                    ClusterAggregates = new List<GroupAggregatesDto>(),
                    TopCompaniesScoresOnComparativeGroup = new List<CompanyScoreDto>()
                };

                for (var cluster = 0; cluster < clustersCount; cluster++)
                {
                    var clustersRatings = group.Value.Select(x => x.ClustersRatings[cluster]);

                    groupRating.ClusterAggregates.Add(new GroupAggregatesDto
                    {
                        Avg = clustersRatings.Average(),
                        Max = clustersRatings.Max(),
                        Min = clustersRatings.Min()
                    });
                }

                if (includeTopPerformersInfo)
                {
                    List<CompanyScoreDto> groupTopCompanies;                   

                    if (topInGroupsDictionary.TryGetValue(group.Key, out groupTopCompanies))
                    {
                        groupRating.TopCompaniesScoresOnComparativeGroup.AddRange(groupTopCompanies);
                    }
                }

                groupRatings.Add(groupRating);
            }


            return groupRatings;
        }


        float conditionalDiv(float filterSign, float filterDiff, float a, float b)
        {
            return (a / b) * filterSign + filterDiff;
        }

        float conditionalReturn(float filterSign, float filterDiff, float a)
        {
            return a * filterSign + filterDiff;
        }

        //pentru companiile care nu apartin de univers sau nu respecta filtrul rating = -4 
        //(la fel si pentru e,s, g si disjunct clusters) 


        void ComputeRanking(RatingsResult result, ESGSet set, ESGRatingsOptions options, byte filter, int[] clusterGroupHeads)
        {
            int internalUniverseId;

            if (!set.Universes.TryGetValue(options.RefUnivId, out internalUniverseId))
            {
                throw new UnknownUniverseException(options.RefUnivId);
            }

            //int* rankings = result.rankings;
            //float* mins = result.group_mins;
            //float* maxs = result.group_maxs;
            //float* avgs = result.group_avgs;
            //int* groupsCounts = result.group_counts;

            int[] groupsCounts = result.Group_Counts;


            int companiesNo = result.CompaniesCount;


            int clusterGroupHeadsCount = clusterGroupHeads.Length;
            int[] segmentedGroupCount = new int[clusterGroupHeadsCount + 1]; // (int*)calloc((clusterGroupHeadsCount + 1), sizeof(int));


            byte[] universe = set.EncodedUniverses[internalUniverseId];
            
            int[] companiesMap = result.CompanyIndices;


            int nextChangeIndex = 0;
            int currentHeadIndex = -1;
            int rank = 0;

            for (int i = 0; i <= companiesNo; i++)
            {
                if (i == nextChangeIndex) //head group change
                {
                    if (i != 0)
                    {
                        segmentedGroupCount[currentHeadIndex] = rank;
                    }

                    if (i == companiesNo)
                    {
                        break;
                    }

                    rank = 0;
                    nextChangeIndex = companiesNo;

                    ++currentHeadIndex;

                    if (currentHeadIndex < clusterGroupHeadsCount)
                    {
                        nextChangeIndex = clusterGroupHeads[currentHeadIndex];
                    }
                }

                //if (universe[companiesMap[i]] == 1 && ratings[i] >= -3
                //    && ((hostAttributes[companiesMap[i]] & filter) == filter || (!filter)))
                if (universe[companiesMap[i]] == 1 && result.Ratings[i] >= -3 
                    && ((set.CompanyAttributes[companiesMap[i]] & filter) == filter || (filter != 0)))
                {
                    ++rank;

                    result.IsInReferenceUniverse[companiesMap[i]] = true;
                }
                else
                {
                    result.IsInReferenceUniverse[companiesMap[i]] = false;
                }
            }

            //float* currentMin = mins;
            //float* currentMax = maxs;
            //float* currentAvg = avgs;
            //int* currentGroupCount = groupsCounts;
            int clustersCount = set.IndicatorsConfiguration.ClusterCount;
            int groupIndex = 0;

            for (int clusterIndex = 0; clusterIndex < clustersCount; clusterIndex++)
            {
                int baseIndex = clusterIndex * companiesNo;
                nextChangeIndex = 0;
                currentHeadIndex = -1;
                float sum = 0;
                rank = 0;
                int computedRank = 1;

                //*currentMin = -1;
                //*currentMax = -1;
                //*currentAvg = -1;
                result.Group_Mins[groupIndex] = -1;
                result.Group_Maxs[groupIndex] = -1;
                result.Group_Avgs[groupIndex] = -1;

                for (int i = 0; i <= companiesNo; i++)
                {
                    if (i == nextChangeIndex) // head group change
                    {
                        if (i != 0)
                        {
                            if (rank > 0)
                            {
                                result.Group_Avgs[groupIndex] = sum / rank;
                            }
                            else
                            {
                                result.Group_Avgs[groupIndex] = -1;
                            }

                            //++currentAvg;
                            //++currentMax;
                            //++currentMin;
                            groupIndex++;

                            //*currentMin = -1;
                            //*currentMax = -1;
                            //*currentAvg = -1;

                            if (groupIndex < result.Group_Avgs.Length)
                            {
                                result.Group_Mins[groupIndex] = -1;
                                result.Group_Maxs[groupIndex] = -1;
                                result.Group_Avgs[groupIndex] = -1;
                            }
                        }

                        if (i == companiesNo)
                            break;

                        sum = 0;
                        rank = 0;
                        computedRank = 1;

                        nextChangeIndex = companiesNo;
                        ++currentHeadIndex;

                        if (currentHeadIndex < clusterGroupHeadsCount)
                        {
                            nextChangeIndex = clusterGroupHeads[currentHeadIndex];
                        }
                    }

                    if (i == companiesNo)
                        break;

                    int currentIndex = baseIndex + i;

                    float rating = result.Ratings[currentIndex];

                    if (rating <= -3 || !((set.CompanyAttributes[companiesMap[currentIndex]] & filter) == filter || (filter != 0)))
                    {
                        result.Rankings[currentIndex] = -1;

                        result.Ratings[currentIndex] = (-1) * (rating + 4); //reverse filter transformation

                        continue;
                    }

                    if (universe[companiesMap[currentIndex]] == 1)
                    {
                        ++rank;

                        if (result.Group_Maxs[groupIndex] == -1)
                        {
                            result.Group_Maxs[groupIndex] = rating;
                        }

                        result.Group_Mins[groupIndex] = rating;

                        sum += rating;
                    }

                    if (i == 0 || (rating != result.Ratings[currentIndex - 1]))
                    {
                        if (universe[companiesMap[currentIndex]] == 1)
                        {
                            computedRank = rank;
                        }
                        else
                        {
                            computedRank = rank + 1;
                        }
                    }

                    if (universe[companiesMap[currentIndex]] == 1)
                    {
                        result.Group_Counts[currentIndex] = segmentedGroupCount[currentHeadIndex];
                    }
                    else
                    {
                        result.Group_Counts[currentIndex] = segmentedGroupCount[currentHeadIndex] + 1;
                    }

                    result.Rankings[currentIndex] = computedRank;

                    result.Percentile[currentIndex] = ComputePercentile(result.Group_Counts[currentIndex], computedRank);
                }

            }

        }

        private float ComputePercentile(int currentGroupCount, int computedRank)
        {
            return (currentGroupCount > 1) ? (float)(currentGroupCount - computedRank) * 100 / (currentGroupCount - 1) : 100;
        }

        private static void GetExternalId(ref Guid? setId)
        {
            if (!setId.HasValue || setId.Equals(Guid.Empty))
                setId = Guid.NewGuid();
        }


        private Guid AddESGSet(Guid? setId, ICollection<ESGCompanyEntity> companies,
            IndicatorsConfiguration indicatorsConfiguration, bool isActive, Guid? defaultMatrixId)
        {
            if (companies == null)
            {
                throw new ArgumentNullException(nameof(companies));
            }
            if (indicatorsConfiguration == null)
            {
                throw new ArgumentNullException(nameof(indicatorsConfiguration));
            }

            GetExternalId(ref setId);

            var industryGroupSet = new IndustryGroupSet(indicatorsConfiguration, companies, isActive, defaultMatrixId);
            _esgSetList.TryAdd(setId.Value, industryGroupSet);

            var peerGroupSet = new PeerGroupSet(indicatorsConfiguration, companies, isActive, defaultMatrixId);

            _esgSetList.TryAdd(setId.Value, peerGroupSet);

            return setId.Value;
        }

        private GovernanceSet AddGovernanceSet(ref Guid? setId, ICollection<GovernanceCompanyEntity> companies,
            IndicatorsConfiguration indicatorConfiguration)
        {
            GetExternalId(ref setId);
            var newSet = new GovernanceSet(indicatorConfiguration, companies);
            _esgSetList.TryAdd(setId.Value, newSet);
            return newSet;
        }

        private IEnumerable<Guid> DeleteSets<T>() where T : IComputationSet
        {
            _rwlSyncObject.EnterWriteLock();
            try
            {
                var deletedSetGuids = new List<Guid>();
                var governanceSets = _esgSetList.GetSetList<T>();

                foreach (var set in governanceSets)
                {
                    deletedSetGuids.AddRange(_esgSetList.GetSetId(set.Id));
                }

                _esgSetList.Delete(deletedSetGuids);

                return deletedSetGuids;
            }
            finally
            {
                _rwlSyncObject.ExitWriteLock();
            }
        }

        // The bulk of the clean-up code is implemented in Dispose(bool)
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                // free managed resources
            }
            // free native resources if there are any.
        }

        //TODO: test if still valid
        private IEnumerable<Guid> GetItems(Guid? setId, Func<IComputationSet, IEnumerable<Guid>> selection)
        {
            var peerGroupSetIds = new List<Guid>();
            var industryGroupSetIds = new List<Guid>();

            if ((setId != null && (setId.Value.Equals(Guid.Empty))) || (setId == null))
            {
                IEnumerable<ESGSet> esgSets = _esgSetList.GetSetList<ESGSet>();
                foreach (var set in esgSets.Where(set => selection(set).Any()))
                {
                    peerGroupSetIds.AddRange(selection(set));
                }

                //foreach (var set in _esgSetList.GetSetList())
                //{
                //    peerGroupSetIds.AddRange(selection(set));
                //}
                //foreach (var set in _esgSetList.GetSetList())
                //{
                //    industryGroupSetIds.AddRange(selection(set));
                //}
            }
            else
            {
                IComputationSet peerGroupSet;
                _esgSetList.GetSetByGuid(setId.Value, typeof(PeerGroupSet), out peerGroupSet);

                peerGroupSetIds.AddRange(selection(peerGroupSet));
            }

            var verificationSet = industryGroupSetIds.Except(peerGroupSetIds);
            if (!verificationSet.IsNullOrEmpty())
            {
                throw new ESGComputeException("Comparative Group missmatch", null);
            }
            return peerGroupSetIds;
        }
    }
}